﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using VsxFactory.Modeling.VisualStudio;
using System.Diagnostics.Contracts;
using Microsoft.VisualStudio.Modeling;
using System.ComponentModel;
using System.Diagnostics;

namespace VsxFactory.Modeling.Strategies.Rules
{
    public abstract class GenerationRule
	{
        /// <summary>
        /// Condition d'activation de la règle.
        /// </summary>
        public List<IFilter> Condition { get; private set; }
        /// <summary>
        /// Permet de supprimer le fichier précédemment généré lors du changement de valeur d'une propriété.
        /// </summary>
        [System.ComponentModel.DesignerSerializationVisibility(System.ComponentModel.DesignerSerializationVisibility.Hidden)]
		public Func<IStrategyContext, GenerationSpec, bool> MustDeleteOldGeneratedArtefactOnPropertyChange { get; set; }
        /// <summary>
        /// Permet de spécifier si la génération de code se fait sur un changement de valeur d'une propriété.
        /// </summary>
		[System.ComponentModel.DesignerSerializationVisibility(System.ComponentModel.DesignerSerializationVisibility.Hidden)]
		public Func<IStrategyContext, GenerationSpec, bool> ShouldGenerateCodeOnPropertyChange { get; set; }
        /// <summary>
        /// Execution de code aprés l'exécution de la régle (et de ses sous-régles).
        /// </summary>
		[System.ComponentModel.DesignerSerializationVisibility(System.ComponentModel.DesignerSerializationVisibility.Hidden)]
		public Action<IStrategyContext, GenerationSpec> PostGenerationAction { get; set; }
        /// <summary>
        /// Permet de forcer l'élément sur lequel porte la génération.
        /// </summary>
		[System.ComponentModel.DesignerSerializationVisibility(System.ComponentModel.DesignerSerializationVisibility.Hidden)]
		public Func<GenerationSpec, GenerationSpec> TargetElement { get; set; }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="melType"></param>
		public GenerationRule(Type melType=null)
		{
            Condition = new List<IFilter>();
            if( melType != null)
                Condition.Add(new CheckElement() { TargetType = melType });
            PostGenerationAction = (c, e) => { };
			MustDeleteOldGeneratedArtefactOnPropertyChange = (ctx, wrapper) => false;
			ShouldGenerateCodeOnPropertyChange = (ctx, wrapper) => ctx.PropertyId == ctx.Element.GetDomainClass().NameDomainProperty.Id;
			TargetElement = w => w;
			Generators = new List<INodeGenerator>();
			NestedRules = new List<GenerationRule>();
		}

        /// <summary>
        /// 
        /// </summary>
        /// <param name="ctx"></param>
        /// <param name="wrapper"></param>
        /// <returns></returns>
		internal bool IsConditionTrue(IStrategyContext ctx, GenerationSpec wrapper)
		{
			try
			{
				return wrapper.ModelElement != null && Condition.TrueForAll(f => f.Execute(ctx, wrapper));
			}
			catch 
			{ 
				return false; 
			}
		}

		#region Abstract methods
        public abstract bool Generate(IStrategyContext context, GenerationSpec targetElement, IVsProjectFileItem node);
		public abstract void Remove(IStrategyContext context , GenerationSpec targetElement);
		public abstract void Change(IStrategyContext context, GenerationSpec candidateElement, GenerationSpec targetElement, Guid propertyId, object oldValue);
        public abstract IVsProjectFileItem GetCurrentNode(IStrategyContext context, GenerationSpec targetElement);
		#endregion

		#region Properties
		public List<GenerationRule> NestedRules { get; private set; }
		public virtual string Name {get { return this.GetType().Name; }}
		public virtual bool OverwriteFile { get; set; }
		public List<INodeGenerator> Generators { get; private set; }
		public string OutputFileNameFormat { get; set; }
		#endregion
	}

	public class GenerationSpec
	{
		public Guid ProjectElementGuid { get; set; }
		private IVsProject projectNode;

		public GenerationSpec(ModelElement mel, IVsProject projectNode, Guid projectElementGuid)
		{
			Contract.Requires(mel != null);
            if(projectElementGuid == Guid.Empty)
                Debug.WriteLine("Using the default GenerationSpec with no target project set (The current project will be use). Override CreateGenerationSpec in yur strategy to define specific target generation project.");
			this.ModelElement = mel;
			this.ProjectElementGuid = projectElementGuid;
			this.projectNode = projectNode;
		}

		public Microsoft.VisualStudio.Modeling.ModelElement ModelElement { get; private set; }

		public Guid NamePropertyId
		{
			get { return ModelElement.GetDomainClass().NameDomainProperty.Id; }
		}

		public string Name
		{
			get { return DomainClassInfo.GetName(ModelElement); }
		}

		public string FullName
		{
			get {
				return String.Concat(GenerationProject.Namespace, ".", Name);
			}
		}

		public virtual IVsProject GenerationProject
		{
			get
			{
				return projectNode;
			}
		}

        /// <summary>
        /// Essaye pour retrouver une relation supprimée dont le player est du type T
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="element"></param>
        /// <returns></returns>
        protected static T FindDeletedOppositeRole<T>(ModelElement element) where T : ModelElement
        {
            var field = element.Partition.ElementDirectory.GetType().GetField("deletedElements", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
            var v = field.GetValue(element.Partition.ElementDirectory) as Dictionary<Guid, ModelElement>;

            foreach (var mel in v.Values)
            {
                if (mel is ElementLink)
                {
                    var links = ((ElementLink)mel).LinkedElements;
                    if (links.Count == 2)
                    {
                        for (int i = 0; i < 2; i++)
                        {
                            ModelElement e = links[i];
                            if (e.Id == element.Id)
                            {
                                var e2 = i == 0 ? links[1] : links[0];
                                if (e2 is T)
                                    return e2 as T;
                            }
                        }
                    }
                }
            }
            return default(T);
        }
	}

}
